//
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// This file declares cast functions between strings and numeric types, with
// format strings.

#ifndef ZETASQL_PUBLIC_FUNCTIONS_CONVERT_STRING_WITH_FORMAT_H_
#define ZETASQL_PUBLIC_FUNCTIONS_CONVERT_STRING_WITH_FORMAT_H_

#include <optional>
#include <string>
#include <vector>

#include "zetasql/public/value.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "zetasql/base/status.h"

namespace zetasql {
namespace functions {

namespace internal {  // For internal use only

// The enum for format elements.
// Some format elements are case sensitive, e.g. "x" is DIGIT_X_LOWER, and "X"
// is DIGIT_X_UPPER. For format elements that consist of multiple characters,
// the case of the first character determines the case of the format
// element. Thus, "eeee" is EXPONENT_EEEE_LOWER, so are "eEee", "eEEE", "eEEe"
// etc.
enum class FormatElement {
  kCurrencyDollar,  // $
  kCurrencyCLower,  // c, lowercase
  kCurrencyCUpper,  // C, uppercase
  kCurrencyL,       // L

  kDigit0,       // 0
  kDigit9,       // 9
  kDigitXLower,  // x, lowercase
  kDigitXUpper,  // X, uppercase

  kDecimalPointDot,  // .
  kDecimalPointD,    // D

  kGroupSeparatorComma,  // ,
  kGroupSeparatorG,      // G

  kSignS,   // S
  kSignMi,  // MI
  kSignPr,  // PR

  kRomanNumeralLower,  // rn, lowercase
  kRomanNumeralUpper,  // RN, uppercase

  kExponentEeeeLower,  // eeee, lowercase
  kExponentEeeeUpper,  // EEEE, uppercase

  kElementB,  // B

  kElementV,  // V

  kCompactMode,  // FM

  kTmLower,   // tm, lowercase
  kTmUpper,   // TM, uppercase
  kTm9Lower,  // tm9, lowercase
  kTm9Upper,  // TM9, uppercase
  kTmeLower,  // tme, lowercase
  kTmeUpper,  // TME, uppercase
};

// The type of the output that is specified by a format string.
enum class OutputType
{
  // Text Minimal, e.g. "TM", "TM9"
  kTextMinimal,

  // Roman numeral, e.g. "RN", "RNFM"
  kRomanNumeral,

  // Hexadecimal, e.g. "00X0X0"
  kHexadecimal,

  // Decimal, e.g. "9.999"
  kDecimal,
};

// Represents the result of parsing a format string. It contains information
// that is used to generate the output.
struct ParsedFormatElementInfo {
  // Type of the output specified by the format string.
  OutputType output_type;

  // Contains the list of elements that are used to generate the output.
  std::vector<FormatElement> elements;

  // The number of digits before the decimal point.
  size_t num_integer_digit = 0;

  // Scale, i.e. the number of digits after the decimal point.
  size_t scale = 0;

  // Indicates whether the format string contains "EEEE".
  bool has_exponent = false;

  // Indicates whether the format string contains 'FM'.
  bool has_fm = false;

  // Indicates whether the format string contains 'B'.
  bool has_b = false;

  // The index of the first kDigit0 in <elements>. It is used to generate
  // leading zeros.
  std::optional<int> index_of_first_zero;

  // The index in <elements> where the decimal point is, i.e. after the last
  // integer digit.
  // For example,
  // - if elements is "99.999", decimal_point_index is 2.
  // - if elements is "99EEEE", decimal_point_index is 2.
  size_t decimal_point_index = 0;

  // The decimal point in the format string.
  std::optional<FormatElement> decimal_point;

  // The currency element in the format string.
  std::optional<FormatElement> currency;

  // The sign element in the format string.
  std::optional<FormatElement> sign;

  // Indicates if the 'S' is at the front. e.g. "S9.999EEE". Effective only when
  // sign is kSignS.
  bool sign_at_front = false;

  // The text minimal format element in the format string.
  std::optional<FormatElement> tm;

  // The roman numeral element in the format string.
  std::optional<FormatElement> roman_numeral;
};

// Represents the result of parsing the string generated by FORMAT(number).
// For example, for number string "-12.345e+67", ParsedNumberString will be:
// - integer_part = "12"
// - fractional_part = "345"
// - exponent = "+67"
// - negative = true
// - is_infinity_or_nan = false
// TODO: This struct should be in convert_string_with_format.cc. It's
// in this file for now so that it can tested. Move it to the .cc file after
// output generation is implemented.
struct ParsedNumberString {
  // Integer part. Empty when the integer part is 0, or if the input is NaN
  // or Inf.
  std::string integer_part;

  // Fractional part. Can be empty. It does not contain the decimal
  // point. E.g. if the number string is "12.345", its value is "345".
  std::string fractional_part;

  // Exponent part, such "+01", "-01", "+00" etc. Can be empty.
  std::string exponent;

  // Indicates if the number if negative.
  bool negative = false;

  // Indicates if the number if Inf.
  bool is_infinity = false;

  // Indicates if the number if NaN.
  bool is_nan = false;
};

// Parses the string generated by FORMAT(number) to generate
// ParsedNumberString. Since <number_string> is generated by FORMAT() using a
// format string that we control, it's guaranteed to be in lower-case letters
// and well-formed.
// TODO: Move it to convert_string_with_format.cc after output
// generation is implemented.
absl::StatusOr<ParsedNumberString> ParseFormattedRealNumber(
    absl::string_view number_string);

// Parses the <format> string and returns the ParsedFormatElementInfo.
// TODO: This method is for testing purpose: the case handling can only
// be tested by this method: cases don't affect validation, only output. Once we
// start generating outputs, this method is not needed. Case handling will be
// fully covered by NumericalToStringWithFormat() by then. Remove this method
// once output generation is implemented.
absl::StatusOr<ParsedFormatElementInfo> ParseForTest(absl::string_view format);

// Returns the string representation of the format element. This is intended to
// be used to generate error messages.
std::string FormatElementToString(FormatElement element);

}  // namespace internal

// The class that is used to format a numerical value to string using a format
// string.
//
// See (broken link) for documented behavior.
class NumericalToStringFormatter {
 public:
  explicit NumericalToStringFormatter(ProductMode product_mode,
                                      bool canonicalize_zero = false)
      : product_mode_(product_mode), canonicalize_zero_(canonicalize_zero) {}

  NumericalToStringFormatter(const NumericalToStringFormatter&) = delete;
  NumericalToStringFormatter& operator=(const NumericalToStringFormatter&) =
      delete;

  // Set the format string.
  absl::Status SetFormatString(absl::string_view format);

  // Format the value <v> using the format string.
  // Precondition:
  // - SetFormatString() must have been called,
  // - <v> cannot be a NullValue,
  // - the type of <v> has to be numerical.
  absl::StatusOr<std::string> Format(const Value& v);

 private:
  ProductMode product_mode_;
  // If true, the sign on a signed zero is removed when converting numeric type
  // to string.
  // TODO: remove this flag when all engines have
  // rolled out this new behavior.
  const bool canonicalize_zero_ = false;
  std::optional<internal::ParsedFormatElementInfo> parsed_info_;
};

// Validates the format string used in CAST() from a numerical type to string.
absl::Status ValidateNumericalToStringFormat(absl::string_view format);

// Shorthand for doing the format in one call.
absl::StatusOr<std::string> NumericalToStringWithFormat(
    const Value& v, absl::string_view format, ProductMode product_mode,
    bool canonicalize_zero = false);

}  // namespace functions
}  // namespace zetasql

#endif  // ZETASQL_PUBLIC_FUNCTIONS_CONVERT_STRING_WITH_FORMAT_H_
